# NRSFlow.py
'''
作者：赵宗义，xinshengzzy@foxmail.com
'''
from simulators.BaseSimulator import BaseSimulator
import mytools
from network.pkt_tools import get_flow_id
from scapy.all import *
import my_constants as mc
import json
import random
from 翻译包 import *
from 文件工具箱 import *

class NRSFlow(BaseSimulator):
	真实流集 = 字典()
	参考阈值 = 0
	'''测量时间窗口长度: 1分钟, 
	链路类型: OC-24 (带宽为622Mbps), 数据包平均长度: 1074字节 (与CAIDA文件相同)'''
	def __init__(self, 内存方案 = "HashFlow:1.0MB", 协议 = "ipv4"):
		super(HashFlow, self).__init__() 
		# 内存方案也可以是一个整数或者浮数, 表示内存的大小, 单位是MB
		assert(协议 == "ipv4" or 协议 == "ipv6")
		if isinstance(内存方案, str):
			self.配置内存方案(内存方案)
			self.表长1 = mc.表长1
			self.表长2 = mc.表长2
			self.表长3 = mc.表长3
			self.表长4 = mc.表长4
		elif isinstance(内存方案, int) or isinstance(内存方案, float):
			# 此时内存方案的单位为MB.
			if "ipv4" == 协议:
				self.表长4 = 整数(0.5*内存方案*1024.0*1024.0/19)
			elif "ipv6" == 协议:
				self.表长4 = 整数(0.5*内存方案*1024.0*1024.0/43)
			self.表长1 = 整数(self.表长4/3)
			self.表长2 = 整数(self.表长4/3)
			self.表长3 = 整数(self.表长4/3)
		else:
			raise SystemExit("你的内存方案为:", 内存方案, ". 该方案不存在.")
		self.协议 = 协议
		self.真实流集 = HashFlow.真实流集
		self.参考阈值 = HashFlow.参考阈值
		self.大流碎片数 = 0
		self.M = []
		self.A0 = []
                self.A1 = []
		for i in range(self.表长1 + self.表长2 + self.表长3):
		        self.M.append([0, 0])
		for i in range(self.表长4):
			self.A0.append([0, 0])
			self.A1.append([0, 0])
		self.导出周期 = 30 # 默认的导出周期是30秒

	def 配置内存方案(self, 内存方案):
		mc.配置内存方案(内存方案)
	
	def 带宽消耗_转_导出周期(self, 带宽消耗):
	# 此处带宽消耗的单位是bps
		if "ipv4" == self.协议:
			哈希桶长度 = 17.0*8 # 这里默认的流标识符是IPv4的五元组, 长度单位为"位"
		elif "ipv6" == self.协议:
			哈希桶长度 = 41.0*8 # 这里默认的流标识符是IPv4的五元组, 长度单位为"位"
		self.导出周期 = (哈希桶长度*(self.表长1+self.表长2+self.表长3))/带宽消耗

	def 处理数据包(self, 标识符, 时间戳):
		'''处理一个新到达的数据包'''
		self.数据包数 += 1
		if 时间戳 - self.导期起始状态 > self.导出周期:
			self.更换数据平面()
			self.导出数据平面()
			self.导期起始状态 = 时间戳

		索引1 = mytools.hash1(标识符.encode('utf-8'))%self.表长1
		索引2 = mytools.hash2(标识符.encode('utf-8'))%self.表长2 + self.表长1
		索引3 = mytools.hash3(标识符.encode('utf-8'))%self.表长3 + self.表长1 + self.表长2
		索引4 = mytools.hash4(标识符.encode('utf-8'))%self.表长4
		摘要 = self.计算摘要(标识符)
		
		最小计数值 = mc.INF
		最小索引 = mc.INF
		标志 = mc.冲突
		for 索引 in [索引1, 索引2, 索引3]:
			if self.M[self.数平指针][索引] == [0, 0]:
				self.M[self.数平指针][索引] = [标识符, 1]
				标志 = mc.空置
				break
			elif self.M[self.数平指针][索引][0] == 标识符:
				self.M[self.数平指针][索引][1] += 1
				标志 = mc.匹配
				break
			else:
				if self.M[self.数平指针][索引][1] < 最小计数值:
					最小计数值 = self.M[self.数平指针][索引][1]
					最小索引 = 索引

		'''将数据包在A表中进行匹配'''
		if mc.冲突 == 标志:
			if 0 == self.A[self.数平指针][索引4][1]:
				self.A[self.数平指针][索引4] = [摘要, 1]
			elif 摘要 == self.A[self.数平指针][索引4][0]:
				self.A[self.数平指针][索引4][1] += 1
				if self.A[self.数平指针][索引4][1] > 最小计数值:
					self.M[self.数平指针][最小索引] = \
						[标识符, self.A[self.数平指针][索引4][1]]
					self.A[self.数平指针][索引4] = [0, 0]
					self.升级次数 += 1
			else: 
				self.A[self.数平指针][索引4] = [摘要, 1]

	def 导出数据平面(self):
		for 标识符, 计数值 in self.M[1 - self.数平指针]:
			if 计数值 > 0:
				if 标识符 not in self.流记录集:
					self.流记录集[标识符] = 0
				self.流记录集[标识符] += 计数值
				if (标识符 in self.真实流集) and (self.真实流集[标识符] >= self.参考阈值):
					self.大流碎片数 += 1
		for 索引 in range(self.表长1 + self.表长2 + self.表长3):
			self.M[1 - self.数平指针][索引] = [0, 0]
		for 索引 in range(self.表长4):
			self.A[1 - self.数平指针][索引] = [0, 0]
	
	def 获取流记录集(self):
		流记录集 = self.获取流集拷贝()
		for 标识符, 计数值 in self.M[self.数平指针]:
			if 计数值 > 0:
				if 标识符 not in 流记录集:
					流记录集[标识符] = 0
				流记录集[标识符] += 计数值
				if (标识符 in self.真实流集) and (self.真实流集[标识符] >= self.参考阈值):
					self.大流碎片数 += 1
		return 流记录集

	def 备份(self, 文件名):
		备份 = 字典()
		备份["流记录集"] = self.获取流记录集()
		备份["升级次数"] = self.升级次数
		备份["数据包数"] = self.数据包数
		备份["大流碎片数"] = self.大流碎片数
		备份["数平导出次数"] = self.数平导出次数
		备份数据结构(备份, 文件名)
